package com.zhulong.mvcByHand.servlet;

import com.zhulong.mvcByHand.annotation.MyAutowired;
import com.zhulong.mvcByHand.annotation.MyController;
import com.zhulong.mvcByHand.annotation.MyRequestMapping;
import com.zhulong.mvcByHand.annotation.MyService;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.logging.Logger;

public class MyDispatcherServlet extends HttpServlet {

    private Logger logger = Logger.getLogger("init");

    private Properties properties = new Properties();

    private List<String> classNames = new ArrayList<>();

    private Map<String,Object> ioc = new HashMap<>();

    private Map<String, Method> handlerMapping = new HashMap<>();

    private Map<String,Object> controllerMap = new HashMap<>();

    @Override
    public void init(ServletConfig config) throws ServletException{
        super.init();
        logger.info("初始化MyDispatcherServlet");
        logger.info("加载配置文件，填充properties字段");
        doLoadConfig(config.getInitParameter("contextConfigLocation"));

        logger.info("根据properties，初始化所有相关联的类，扫描用户设定的包下面的类");
        logger.info("打印出来的包名:"+properties.getProperty("scanPackage"));
        doScanner(properties.getProperty("scanPackage"));

        logger.info("拿到扫描到的类，通过反射机制，实例化，并且放到ioc容器中");
        doInstance();

        logger.info("自动注入依赖");
        doAutowired();

        logger.info("初始化handlerMapping（将url和method对应上）");
        initHandlerMapping();

        doAutowired2();
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException,IOException{
        logger.info("执行MyDispatcherServlet的doPost()");
        try {
            doDispatch(req,res);
        } catch (Exception e){
            res.getWriter().write("500");
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException,IOException{
       // super.doGet(req,res);
        logger.info("执行MyDispatcherServlet的doGet()");
        try {
            doDispatch(req,res);
        } catch (Exception e){
            res.getWriter().write("500");
        }
    }


    private void doDispatch(HttpServletRequest req,HttpServletResponse res) throws Exception{
        if(handlerMapping.isEmpty()){
            return;
        }
        String url =req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replace(contextPath,"").replaceAll("/+","/");
        logger.info("去掉url前面的斜杠“/” ,所有的@MyRequestMapping可以不用写斜杠");
        if(url.lastIndexOf("/") != 0){
            url = url.substring(1);
        }
        if(!this.handlerMapping.containsKey(url)){
            res.getWriter().write("404 not found");
            logger.info("404 not found");
            return;
        }
        Method method = this.handlerMapping.get(url);

        //获取方法的参数列表
        Class<?>[] parameterTypes = method.getParameterTypes();
        //获取请求的参数
        Map<String,String[]> parameterMap = req.getParameterMap();

        //保存参数值
        Object[] paramValues = new Object[parameterTypes.length];
        //方法的参数列表
        for(int i = 0;i<parameterTypes.length;i++){
            //根据参数名做处理
            String requestParam = parameterTypes[i].getSimpleName();
            if(requestParam.equals("HttpServletRequest")){
                paramValues[i] = req;
                continue;
            }
            if(requestParam.equals("HttpServletResponse")){
                paramValues[i] = res;
                continue;
            }
            if(requestParam.equals("String")){
                for(Map.Entry<String,String[]> param:parameterMap.entrySet()){
                    String value = Arrays.toString(param.getValue())
                            .replaceAll("\\[|\\]","").replaceAll(",\\s",",");
                    paramValues[i] = value;
                }
            }

        }
        //利用反射机制来调用
        try {
            //第一个参数是method所对用的实例，在ioc容器中
            method.invoke(this.controllerMap.get(url),paramValues);
        } catch (Exception e){
            e.printStackTrace();
        }
    }









    /**
     * 根据配置文件位置，读取配置文件中的配置信息，将其填充到properties
     * @param location
     */
    private void doLoadConfig(String location){
        logger.info("将web.xml中的contextConfigLocation对应value值的文件加载到流里");
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(location);
        try {
            logger.info("读取"+location+"里面的文件");
            properties.load(resourceAsStream);
        } catch (IOException e){
            e.printStackTrace();
        } finally {
            if(null != resourceAsStream){
                try {
                    resourceAsStream.close();
                } catch (IOException e){
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 将指定包下扫描到的类，添加到classNames字段中
     * @param packageName
     */
    private void doScanner(String packageName){
        URL url = this.getClass().getClassLoader().getResource("/"+packageName.replaceAll("\\.","/"));
        File dir = new File(url.getFile());
        for(File file:dir.listFiles()){
            if(file.isDirectory()) {
                logger.info("递归读取包");
                doScanner(packageName + "." + file.getName());
            }else{
                String className = packageName + "." + file.getName().replace(".class", "");
                classNames.add(className);
            }
        }
    }

    /**
     * 将classNames中的类实例化，经key-value:类名小写-类对象放入ioc字段中
     */
    private void doInstance(){
        if(classNames.isEmpty()){
            return;
        }
        for(String className:classNames){
            try {
                logger.info("获取类，反射来实例化,只有加@MyController需要实例化");
                Class<?> clazz = Class.forName(className);
                if(clazz.isAnnotationPresent(MyController.class)){
                    ioc.put(toLowerFirstWord(clazz.getSimpleName()),clazz.newInstance());
                }else if(clazz.isAnnotationPresent(MyService.class)){
                    MyService myService = clazz.getAnnotation(MyService.class);
                    String beanName = myService.value();
                    if("".equals(beanName.trim())){
                        beanName = toLowerFirstWord(clazz.getSimpleName());
                    }

                    Object instance = clazz.newInstance();
                    ioc.put(beanName,instance);
                    Class[] interfaces = clazz.getInterfaces();
                    for(Class<?> i:interfaces){
                        ioc.put(i.getName(),instance);
                    }
                }
            }catch (Exception e){
                e.printStackTrace();
                continue;
            }
        }
    }

    /**
     * 自动化的依赖注入
     */
    private void doAutowired(){
        if(ioc.isEmpty()){
            return;
        }
        for(Map.Entry<String,Object> entry:ioc.entrySet()){
            logger.info("包括私有的方法，在spring中没有隐私，@MyAutowired可以注入public，private字段");
            Field[] fileds = entry.getValue().getClass().getDeclaredFields();
            for(Field field:fileds){
                if(!field.isAnnotationPresent(MyAutowired.class)){
                    continue;
                }
                MyAutowired autowired = field.getAnnotation(MyAutowired.class);
                String beanName = autowired.value().trim();
                if("".equals(beanName)){
                    beanName = field.getType().getName();
                }
                field.setAccessible(true);
                try {
                    field.set(entry.getValue(),ioc.get(beanName));
                }catch (Exception e){
                    e.printStackTrace();
                    continue;
                }
            }
        }
    }

    private void doAutowired2(){
        if(controllerMap.isEmpty()){
            return;
        }
        for(Map.Entry<String,Object> entry:controllerMap.entrySet()){
            logger.info("包括私有的方法，在spring中没有隐私，@MyAutowired可以注入public，private字段");
            Field[] fileds = entry.getValue().getClass().getDeclaredFields();
            for(Field field:fileds){
                if(!field.isAnnotationPresent(MyAutowired.class)){
                    continue;
                }
                MyAutowired autowired = field.getAnnotation(MyAutowired.class);
                String beanName = autowired.value().trim();
                if("".equals(beanName)){
                    beanName = field.getType().getName();
                }
                field.setAccessible(true);
                try {
                    field.set(entry.getValue(),ioc.get(beanName));
                }catch (Exception e){
                    e.printStackTrace();
                    continue;
                }
            }
        }
    }

    private void initHandlerMapping(){
        if(ioc.isEmpty()){
            return;
        }
        try {
            for(Map.Entry<String,Object> entry:ioc.entrySet()){
                Class<? extends Object> clazz = entry.getValue().getClass();
                if(!clazz.isAnnotationPresent(MyController.class)){
                    continue;
                }

                logger.info("拼接url时，是controller头的url拼上方法上的url");
                String baseUrl = "";
                if(clazz.isAnnotationPresent(MyRequestMapping.class)){
                    MyRequestMapping myRequestMapping = clazz.getAnnotation(MyRequestMapping.class);
                    baseUrl = myRequestMapping.value();
                }

                Method[] methods = clazz.getMethods();
                for(Method method:methods){
                    if(!method.isAnnotationPresent(MyRequestMapping.class)){
                        continue;
                    }
                    MyRequestMapping annocation = method.getAnnotation(MyRequestMapping.class);
                    String url = annocation.value();
                    url = (baseUrl+"/"+url).replaceAll("/+","/");
                    handlerMapping.put(url,method);
                    controllerMap.put(url,clazz.newInstance());
                    logger.info(url+","+method);
                }
            }
        } catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 将字符串中的首字母小写
     * @param name
     * @return
     */
    private String toLowerFirstWord(String name){
        char[] charArray = name.toCharArray();
        charArray[0] += 32;
        return String.valueOf(charArray);
    }
}
